package main

import (
	"flag"
	"fmt"
	"net"

	"github.com/pushdotccgzs/coapmsg"
)

//const remoteAddr string = "112.93.129.156:5683"

var Imei string
var remoteAddr string
var raddr *net.UDPAddr
var glsock *net.UDPConn
var gltoken string
var gldata chan string

func sendCoAP(msg coapmsg.Message) {
	fmt.Printf("SEND\n%s\n", ShowCoAP(msg))
	buf, err := coapmsg.MessageToBytes(msg)
	if err != nil {
		fmt.Println("message serialize failed.")
		return
	}

	snd, err := glsock.Write(buf)
	if err != nil {
		fmt.Println("send coap to client failed.", err)
		return
	}
	fmt.Printf("SEND [%d][%s]\n", snd, byte2Hex(buf))
}

func byte2Hex(data []byte) string {
	var rsp string
	for _, val := range data {
		rsp += fmt.Sprintf("%02X", val)
	}
	return rsp
}

func DeviceRegister() (err error) {
	regMsg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageConfirmable,
		Code:        coapmsg.Post,
		MessageID:   coapmsg.GenerateMessageID(),
	}
	regMsg.AddOption(coapmsg.OptionURIPath, "t")
	regMsg.AddOption(coapmsg.OptionURIPath, "r")
	regMsg.AddOption(coapmsg.OptionURIQuery, fmt.Sprintf("ep=%s", Imei))
	sendCoAP(regMsg)

	rcvMsg1, err := ReadServerData()
	fmt.Printf("RECV %s\n", ShowCoAP(rcvMsg1))

	rcvMsg2, err := ReadServerData()
	fmt.Printf("RECV %s", ShowCoAP(rcvMsg2))
	gltoken = string(rcvMsg2.GetToken())
	return nil
}

func ReadServerData() (msg coapmsg.Message, err error) {
	buf := make([]byte, 1024)
	length, servAddr, err := glsock.ReadFromUDP(buf)
	if err != nil {
		fmt.Println("read from udp first failed.", err)
		return
	}
	fmt.Println("recv from addr: ", servAddr.String())

	dstbuf := make([]byte, length)
	copy(dstbuf, buf)
	return coapmsg.BytesToMessage(dstbuf)
}

func parseFlag() (err error) {
	flag.StringVar(&Imei, "imei", "", "device imei")
	flag.StringVar(&remoteAddr, "host", "", "remote server host, like 192.168.0.110:5683")

	flag.Parse()
	if Imei == "" {
		err = fmt.Errorf("IMEI cannot found.")
		return
	}

	if remoteAddr == "" {
		err = fmt.Errorf("ADDR cannot found.")
		return
	}
	return
}

func main() {
	err := parseFlag()
	if err != nil {
		fmt.Println(err)
		return
	}

	raddr, err := net.ResolveUDPAddr("udp4", remoteAddr)
	if err != nil {
		fmt.Println("resolve udp addr failed.", err)
		return
	}
	glsock, err = net.DialUDP("udp4", nil, raddr)
	if err != nil {
		fmt.Println("DialUDP failed.", err)
		return
	}
	fmt.Println("dial udp succ.")
	DeviceRegister()
	fmt.Println("")

	gldata = make(chan string)
	go dataReporter()
	go IncomingUDPData()

	var line string
	for {
		fmt.Scanln(&line)
		if line == "" {
			continue
		}
		gldata <- line
		line = ""
	}
}

/*
	MessageType uint8
	Code        CoapCode
	MessageID   uint16
	Payload     MessagePayload
	Token       []byte
	Options     []Option
*/
func rspACK(msgid uint16, token []byte) {
	rspMsg := &coapmsg.CoapMessage{
		MessageType: coapmsg.MessageAcknowledgment,
		Code:        coapmsg.CoapCodeCreated,
		MessageID:   msgid,
		Token:       token,
	}
	sendCoAP(rspMsg)
}

func IncomingUDPData() {
	for {
		msg, err := ReadServerData()
		if err != nil {
			fmt.Println("read udp coap data failed.", err)
			break
		}
		fmt.Printf("RECV: %s\n", ShowCoAP(msg))
		rspACK(msg.GetMessageId(), msg.GetToken())
	}
}

func dataReporter() {
	for {
		select {
		case line := <-gldata:
			fmt.Printf("prepare data report. %s\n", line)
			dataMsg := &coapmsg.CoapMessage{
				MessageType: coapmsg.MessageNonConfirmable,
				Code:        coapmsg.CoapCodeContent,
				MessageID:   coapmsg.GenerateMessageID(),
				Token:       []byte(gltoken),
			}
			dataMsg.AddOption(6, 0)
			dataMsg.SetStringPayload(line)
			sendCoAP(dataMsg)
		}
	}
}

func MessageTypeString(msg coapmsg.Message) string {
	types := map[uint8]string{
		0: "CON",
		1: "NON",
		2: "ACK",
		3: "RST",
	}
	msgtype := msg.GetMessageType()
	if v, ok := types[msgtype]; ok {
		return v
	} else {
		return "UNKNOWN"
	}
}

func optionString(opt coapmsg.Option) string {
	return fmt.Sprintf("%d %v", opt.GetCode(), opt.GetValue())
	//return fmt.Sprintf("%d %s", opt.GetCode(), opt.GetValue().(string))
}

func ShowCoAP(msg coapmsg.Message) string {
	var rsp string
	rsp = fmt.Sprintf("\t%s %s ID %d TOKEN %d,%s\n", coapmsg.CoapCodeToString(msg.GetCode()), MessageTypeString(msg), msg.GetMessageId(), msg.GetTokenLength(), msg.GetTokenString())
	rsp += "\tOPTIONS "
	opts := msg.GetAllOptions()
	for _, opt := range opts {
		rsp += optionString(opt)
		rsp += " "
	}
	rsp += "\n"
	if msg.GetPayload() != nil {
		rsp += fmt.Sprintf("\tPAYLOAD %d,%s", msg.GetPayload().Length(), byte2Hex(msg.GetPayload().GetBytes()))
	} else {
		rsp += "\tpayload nil"
	}
	return rsp
}
